package com.xyebank.ledger.service.common;

import com.xyebank.ledger.anon.MigTaskComponent;
import com.xyebank.ledger.enums.LedgerJobType;
import com.xyebank.ledger.manage.ManageRedisTool;
import org.modelmapper.internal.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.List;

/**
 * 标准迁移任务调度者（标准的流程）
 */
@SuppressWarnings("rawtypes")
public abstract class MigServiceAdapter<T> implements MigService {

	private Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	private ManageRedisTool redisTool;

	@Autowired
	private DataProvider<T> provider;

	public void migrate() {
		String jobType = getJobType();
		try {
			if (redisTool.isT1Working(jobType)) { // 重入检查
				logger.info("T1正在执行中");
				return;
			}
			redisTool.setT1Working(jobType);
			logger.info("常规数据迁移:{}", jobType);
			int num = 10;
			int sucTotal = 0;
			int targetTotal = 0;
			// TODO 多次0的警戒
			while (true) {
				// TODO 外部中断是放在什么阶段合适呢
				// Thread.sleep(5000);
				Integer index = redisTool.getDBIndex(jobType); // 不指定区间的数据迁移
				List result = provider.get(index, num);
				if (result == null || result.size() == 0) {
					logger.info("迁移处理完毕，当前最新下标{}", index);
					break;
				}
				Integer sucNum = deal(index, result, LedgerJobType.MIGRATE);
				sucTotal += sucNum;
				targetTotal += result.size();
				// 更新下标 存放redis是为了保障进度有地方放
				redisTool.updateDBIndex(jobType, index, result.size()); // 若刚好遇到>10条一直报错，将陷入死循环
				redisTool.setT1Working(jobType);
				redisTool.setWorkStatus(jobType);
				redisTool.updateWorkStatus(jobType); // 更新锁，让锁有个自动释放时间
			}
			logger.info("{}-数据迁移结束,本次迁移目标{}条，共成功处理{}条", jobType, targetTotal, sucTotal);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("数据迁移{}异常,即将退出并释放锁", jobType);
		} finally {
			redisTool.setT1StatusFinWork(jobType);
			// 非常重要，如果不释放，在redis不可用的时候将会有严重问题
			redisTool.removeWorkStatus(getJobType()); // 释放锁

			 redisTool.rmx(jobType);
		}
	}

	/**
	 * 指定时间段进行迁移 1统计有多少 2按个数进行处理
	 * 
	 * @param dateStart
	 * @param dateEnd
	 */
	public void migrate(Date dateStart, Date dateEnd) {
		try {
			Assert.notNull(dateStart, "起始时间不能为空");
			Assert.notNull(dateEnd, "结束时间不能为空");
			Integer count = provider.count(dateStart, dateEnd);
			logger.info("指定时间段补迁数据，日期从{}到{},共{}条", dateStart, dateEnd, count);
			int perNum = 10;
			int sucTotal = 0;
			for (int i = 0; perNum * i <= count; i++) {
				int iFrom = i * perNum; // 起始位置
				int iEnd = (i + 1) * perNum; // 结束位置
				logger.debug("获取数据，从第{}到{}", iFrom, iEnd);
				List<T> result = provider.get(dateStart, dateEnd, iFrom, perNum);
				Integer sucNum = deal(0, result, LedgerJobType.NOT_MIGRATE);
				sucTotal += sucNum;
			}
			logger.info("{}-指定时间段数据迁移结束,本次迁移目标{}条，共成功处理{}条", getJobType(), count, sucTotal);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("获取{}数据库下标异常,即将退出并释放锁进行尝试", getJobType());
		}
	}

	/**
	 * 指定区间段进行迁移
	 * 
	 * @param fromIdx
	 * @param tarNum
	 */
	public void migrate(Integer fromIdx, Integer tarNum) {
		// logger.debug("准开始迁移任务:{}", desc);
		int index = 0;
		int num = 10; // dubbo使用netty，所以有netty maxpayload 8388608限制
		if (tarNum != null) {
			num = tarNum.intValue();
		}

		try {
			if (fromIdx != null && fromIdx.intValue() != 0) { // 指定区间
				logger.debug("指定区间迁移");
				index = fromIdx;
			} else {
				logger.debug("没有指定区间的迁移");
				index = redisTool.getDBIndex(getJobType()); // 不指定区间的常规
			}
			List<T> result = provider.get(index, num);
			deal(index, result, LedgerJobType.MIGRATE);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("获取{}数据库下标异常,即将退出并释放锁进行尝试", getJobType());
			// 非常重要，如果不释放，在redis不可用的时候将会有严重问题
			redisTool.removeWorkStatus(getJobType());
		}
	}

	public void migrate(List list) {
		try {
			logger.debug("没有指定区间的迁移");
			Integer index = redisTool.getDBIndex(getJobType()); // 不指定区间的常规
			deal(index, list, LedgerJobType.NOT_MIGRATE);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("获取{}数据库下标异常,即将退出并释放锁进行尝试", getJobType());
		}
	}

	public String getJobType() {
		return getClass().getAnnotation(MigTaskComponent.class).type();
	}

	public abstract Integer deal(Integer fromIdx, List list, LedgerJobType jobType);
}
